#ifndef MD3_H
#define MD3_H

#include <cstring>
#include <cmath>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <iostream>

#include "md3tex.h"

#include <vector>

using namespace std;

typedef struct _frame
{
    float min_bounds[3];
    float max_bounds[3];
    float loc_origin[3];
    float radius;
    char name[16];
} frame;

typedef struct _tag
{
    char name[64];
    float origin[3];
    float axis[9];
} tag;

typedef struct _shader
{
    char name[64];
    int index;
} shader;

struct tri
{
    int indexes[3];
};

struct texcoord
{
    float st[2];
};

struct vert
{
    int16_t coord[3];
    uint8_t normal[2];
};

struct surf
{
	int unused;
	char name[64];
    int flags;
    int framenum;
    int shadernum;
    int vertnum;
    int trinum;
    int triofs;
    int shaderofs;
    int st_ofs;
    int vertofs;
    int endofs;
    vector<shader> shaders;
    vector<tri> tris;
    vector<texcoord> st;
    vector<vert> verts;
};

struct surfheader
{
	int unused;
    char name[64];
    int flags;
    int framenum;
    int shadernum;
    int vertnum;
    int trinum;
    int triofs;
    int shaderofs;
    int st_ofs;
    int vertofs;
    int endofs;
};

struct md3
{
    public:
	int unused;
    int version;
    char name[64];
    int flags;
    int framenum;
    int tagnum;
    int surfnum;
    int skinnum;
    int frameofs;
    int tagofs;
    int surfofs;
    int eofofs;
    vector<frame> frams;
    vector<tag> tags;
    vector<surf> surfs;
};

struct md3header
{
    int unused;
    int version;
    char name[64];
    int flags;
    int framenum;
    int tagnum;
    int surfnum;
    int skinnum;
    int frameofs;
    int tagofs;
    int surfofs;
    int eofofs;
};

float a = 0;

md3 attach_models(md3& m, md3& m1)
{
	md3 model;
	model.framenum = m.framenum;
	model.tagnum = m.tagnum + m1.tagnum;
	model.surfnum = m.surfnum + m1.surfnum;
	model.skinnum = m.skinnum + m1.skinnum;
	model.frameofs = 0;
	model.tagofs = 0;
	model.surfofs = 0;
	model.eofofs = 0;
	
	int i = 0;
	while(i<m.tagnum)
	{
		model.tags.push_back(m.tags[i]);
		i++;
	}

	i = 0;
	while(i<m1.tagnum)
	{
		model.tags.push_back(m1.tags[i]);
		i++;
	}

	i = 0;
	while(i<m.surfnum)
	{
		model.surfs.push_back(m.surfs[i]);
		i++;
	}
	i = 0;
	while(i<m1.surfnum)
	{
		int j = 0;
		while(j<m1.surfs[i].vertnum)
		{
			int k = 0;
			while(k<m.tagnum)
			{
				int l = 0;
				while(l<m1.tagnum)
				{
					if(strcmp(m.tags.at(k).name,m1.tags.at(l).name) == 0)
					{
						m1.surfs[i].verts[j].coord[0] += (int16_t)m.tags.at(l).origin[0];
						m1.surfs[i].verts[j].coord[1] += (int16_t)m.tags.at(l).origin[1];
						m1.surfs[i].verts[j].coord[2] += (int16_t)m.tags.at(l).origin[2];

						m1.surfs[i].verts[j].coord[0] = (int16_t)(m1.surfs[i].verts[j].coord[0]*m1.tags.at(l).axis[0] + m1.surfs[i].verts[j].coord[1]*m1.tags.at(l).axis[1]+m1.surfs[i].verts[j].coord[2]*m1.tags.at(l).axis[2]);
						m1.surfs[i].verts[j].coord[1] = (int16_t)(m1.surfs[i].verts[j].coord[0]*m1.tags.at(l).axis[3] + m1.surfs[i].verts[j].coord[1]*m1.tags.at(l).axis[4]+m1.surfs[i].verts[j].coord[2]*m1.tags.at(l).axis[5]);
						m1.surfs[i].verts[j].coord[2] = (int16_t)(m1.surfs[i].verts[j].coord[0]*m1.tags.at(l).axis[6] + m1.surfs[i].verts[j].coord[1]*m1.tags.at(l).axis[7]+m1.surfs[i].verts[j].coord[2]*m1.tags.at(l).axis[8]);
					}
					l++;
				}
				k++;
			}
			j++;
		}
		model.surfs.push_back(m1.surfs[i]);
		i++;
	}
	return model;
}

float convert_normals0(uint8_t* normals)
{
#define pi 3.14159
	float norms[3];
	float lat = normals[0] * (2*pi) / 255;
	float lng = normals[1] * (2*pi) / 255;
#undef pi
	norms[0] = cos(lng) * sin(lat);
	return norms[0];
}

float convert_normals1(uint8_t* normals)
{
#define pi 3.14159
	float norms[3];
	float lat = normals[0] * (2*pi) / 255;
	float lng = normals[1] * (2*pi) / 255;
#undef pi
	norms[1] = sin(lng) * sin(lat);
	return norms[1];
}

float convert_normals2(uint8_t* normals)
{
#define pi 3.14159
	float norms[3];
	float lat = normals[0] * (2*pi) / 255;
	float lng = normals[1] * (2*pi) / 255;
#undef pi
	norms[2] = cos(lat);
	return norms[2];
}

void drawmd3(md3& model, GLuint tex)
{
    int i = 0;
	glLoadIdentity();
    glTranslatef(-1.5,0,-24);
	glRotatef(270,0,0,1);
	glRotatef(-90,0,1,0);
	glRotatef(a,0,0,1);

	while(i<model.surfnum)
	{
		glBindTexture(GL_TEXTURE_2D, tex);
		glBegin(GL_TRIANGLES);
		int j = 0;
	    while(j<model.surfs[i].trinum)
		{
			int tmp = model.surfs[i].tris[j].indexes[0];
			int tmp1 = model.surfs[i].tris[j].indexes[1];
			int tmp2 = model.surfs[i].tris[j].indexes[2];
				
			float norms[3];
			norms[0] = convert_normals0(model.surfs[i].verts[tmp].normal);
			norms[1] = convert_normals1(model.surfs[i].verts[tmp].normal);
			norms[2] = convert_normals2(model.surfs[i].verts[tmp].normal);
			float norms1[3];
			norms1[0] = convert_normals0(model.surfs[i].verts[tmp1].normal);				
			norms1[1] = convert_normals1(model.surfs[i].verts[tmp1].normal);
			norms1[2] = convert_normals2(model.surfs[i].verts[tmp1].normal);
			float norms2[3];
			norms2[0] = convert_normals0(model.surfs[i].verts[tmp2].normal);
			norms2[1] = convert_normals1(model.surfs[i].verts[tmp2].normal);
			norms2[2] = convert_normals2(model.surfs[i].verts[tmp2].normal);

			glNormal3f(norms[0],norms[1],norms[2]);
			glTexCoord2f(model.surfs[i].st[tmp].st[0],1-model.surfs[i].st[tmp].st[1]);
		    glVertex3f(model.surfs[i].verts[tmp].coord[0]/64, model.surfs[i].verts[tmp].coord[1]/64, model.surfs[i].verts[tmp].coord[2]/64);
			glNormal3f(norms1[0],norms1[1],norms1[2]);
			glTexCoord2f(model.surfs[i].st[tmp1].st[0],1-model.surfs[i].st[tmp1].st[1]);
			glVertex3f(model.surfs[i].verts[tmp1].coord[0]/64, model.surfs[i].verts[tmp1].coord[1]/64, model.surfs[i].verts[tmp1].coord[2]/64);
		    glNormal3f(norms2[0],norms2[1],norms2[2]);
			glTexCoord2f(model.surfs[i].st[tmp2].st[0],1-model.surfs[i].st[tmp2].st[1]);
			glVertex3f(model.surfs[i].verts[tmp2].coord[0]/64, model.surfs[i].verts[tmp2].coord[1]/64, model.surfs[i].verts[tmp2].coord[2]/64);

			j++;
		}
		glEnd();
		i++;
	}
}

md3 loadmd3(const char* name)
{
    md3 model;
    if(!name) return model;
    FILE* fp = fopen(name,"rb");
    if(fp != NULL)
    {
        fread(&model, sizeof(md3header), 1, fp);

		fseek(fp,model.frameofs,SEEK_SET);
		frame f;
		int i = 0;
		while(i<model.framenum)
		{
			fread(&f, sizeof(frame),1,fp);
			model.frams.push_back(f);
			i++;
		}

		fseek(fp,model.tagofs,SEEK_SET);
		tag t;
		i = 0;
		while(i<model.tagnum)
		{
			fread(&t, sizeof(tag),1,fp);
			model.tags.push_back(t);
			i++;
		}
		fseek(fp,model.surfofs,SEEK_SET);
		surf s;
		i = 0;
		long pos;
		while(i<model.surfnum)
		{
			pos = ftell(fp);
			fread(&s, sizeof(surfheader),1,fp);

			fseek(fp,pos,SEEK_SET);
			fseek(fp,s.triofs,SEEK_CUR);

			tri tr;
			int j = 0;
			while(j<s.trinum)
			{
				fread(&tr, sizeof(tri),1,fp);
				s.tris.push_back(tr);
				j++;
			}

			fseek(fp,pos,SEEK_SET);
			fseek(fp,s.shaderofs,SEEK_CUR);

			shader sh;
			j = 0;
			while(j<s.shadernum)
			{
				fread(&sh, sizeof(shader),1,fp);
				s.shaders.push_back(sh);
				j++;
			}

			fseek(fp,pos,SEEK_SET);
			fseek(fp,s.st_ofs,SEEK_CUR);

			texcoord st;
			j = 0;
			while(j<s.vertnum)
			{
				fread(&st, sizeof(texcoord),1,fp);
				s.st.push_back(st);
				j++;
			}

			fseek(fp,pos,SEEK_SET);
			fseek(fp,s.vertofs,SEEK_CUR);

			vert v;
			j = 0;
			while(j<s.vertnum)
			{
				fread(&v, sizeof(vert),1,fp);
				s.verts.push_back(v);
				j++;
			}

			fseek(fp,pos,SEEK_SET);
			fseek(fp,s.endofs,SEEK_CUR);

			model.surfs.push_back(s);
			s.st.clear();
			s.tris.clear();
			s.verts.clear();
			s.shaders.clear();
			i++;
		}
        fclose(fp);
        return model;
    }
    else
    {
        cout<<"Error "<<errno<<"\n";
        return model;
    }
}

#endif // MD3_H
